package com.ibm.risk.irmp.common.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.tools.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

/**
 * javac
 * https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html
 */
public class JavaCodeCompilerUtil {
    private static final Logger log = LoggerFactory.getLogger(JavaCodeCompilerUtil.class);

    private String compilerClassPath;

    public boolean compileAndJar(String cmd, File sourcePath, File jarFile) {
        cmd = cmd + " " + sourcePath.getPath() + " " + jarFile + " " + compilerClassPath;
        return exec(cmd);
    }

    public boolean compile(File sourcePath, File targetPath, List<String> compileResult, List<String> errorSourceFiles) {

        List<File> sourceFiles = new ArrayList<File>();
        prepare(sourcePath, targetPath, sourceFiles);

//        sourceFiles.stream().forEach(System.out::println);
        if (!sourceFiles.isEmpty()) {
            String sources = sourceFiles.get(0).getAbsolutePath();
            sources = sources.substring(0, sources.lastIndexOf(File.separator)) + File.separator + "*.java";
            boolean result = compilerJavaFile2(sources, targetPath, compileResult, errorSourceFiles);
            log.info("compiler finished with :" + result);
            return result;
        }

        return false;
    }

    /**
     * 递归获取源码文件
     *
     * @param sourcePath 需要编译的文件夹
     * @param targetPath 编译后class类文件存放目录
     */
    public void prepare(File sourcePath, File targetPath, List<File> sourceFiles) {

        if (!targetPath.exists()) {
            targetPath.mkdirs();
        }
        if (sourcePath != null && sourcePath.exists()) {
            File[] listFiles = sourcePath.listFiles();
            if (null == listFiles || listFiles.length == 0) {
                return;
            }
            for (File file2 : listFiles) {
                // 判断是否是文件夹
                if (file2.isDirectory()) {
                    prepare(file2, targetPath, sourceFiles);
                } else {
                    if (file2.getName().endsWith(".java")) {
                        //将源文件目录中的Java文件加入集合中
                        sourceFiles.add(file2);
                    }
                }
            }
        } else {
            log.error("传入格式未知文件");
        }
    }

    private String buildClassPath() {
        StringBuilder sb = new StringBuilder();
        URLClassLoader parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
        for (URL url : parentClassLoader.getURLs()) {
            String p = url.getFile();
            sb.append(p).append(File.pathSeparator);
        }
        return sb.toString();
    }

    public boolean compilerJavaFile2(String sources, File targerPath, List<String> compileResult, List<String> errorSourceFiles) {
        CmdExecutionUtils.executCmd("javac", 60, "-cp", compilerClassPath, "-d", targerPath.getPath(), sources);
        return true;
//        String command = "/usr/bin/javac -cp " + compilerClassPath + " -d " + targerPath + " " + sources + " > /tomcat/compile.log";
//        return exec(command);
    }

    String cmd = "javac -cp C:\\Users\\IBM-ZH~1\\AppData\\Local\\Temp\\rwa_measure\\jar/rwa-measure-backend-1.0-SNAPSHOT.jar -d C:\\Users\\IBM-ZH~1\\AppData\\Local\\Temp\\rwa_measure\\classes C:\\Users\\IBM-ZH~1\\AppData\\Local\\Temp\\rwa_measure\\source\\com\\ibm\\risk\\rwa\\process\\rule\\*.java";

    public static void main(String[] s) {
        JavaCodeCompilerUtil u = new JavaCodeCompilerUtil();
        boolean exec = u.exec(u.cmd);
        System.out.println(exec);
    }

    private boolean exec(String command) {
        CmdExecutionUtils.executCmd(command, 100);
        return true;
    }

    /**
     * 使用rt.jar中的javax.tools包提供的编译器编译Java文件
     * javax.tools.*
     */
    public boolean compilerJavaFile(List<File> sourceFiles, File targerPath, List<String> compileResult, List<String> errorSourceFiles) {
        boolean result;
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //DiagnosticCollector为诊断侦听器，用于将诊断信息收集在一个列表中

        //可以不设置，为null时默认使用system.err
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
        //自定义编译器读写文件的基本构件块
//        Iterable<? extends JavaFileObject> compilationUnits1 = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(files1));
//        compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits1).call();
//
//        Iterable<? extends JavaFileObject> compilationUnits2 =  fileManager.getJavaFileObjects(files2); // use alternative method
//        // reuse the same file manager to allow caching of jar files
//        compiler.getTask(null, fileManager, null, null, null, compilationUnits2).call();

        //获取lib的项目路径和项目的包: 打包后从spring boot jar包中读取编译需要的jar依赖包
        StringBuilder sb = new StringBuilder();
//        String classPath = Thread.currentThread().getContextClassLoader().getResource(".").getPath();
//        sb.append(classPath);

        //#################### Tomcat  服务器需要设置classpath (pom.xml中使用undertow) #######################
        //#################### undertow服务器不需要设置classpath                       #######################
        //#################### SpringBoot War包执行时不需要设置classpath               #######################
        String libPath = JavaCodeCompilerUtil.class.getResource("/").getPath();
        log.info("libPath: {}", libPath);
        /*if(libPath.indexOf(".war!") > 0) {
            URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
            for (URL url : urlClassLoader.getURLs()) {
                sb.append(url.getFile()).append(File.pathSeparator);
            }
        }*/
//        String classPath = System.getProperty("java.class.path");
//        sb.append(classPath).append(File.pathSeparator);
        sb.append(compilerClassPath);

        String flag = "-d";
        List<String> options = new ArrayList<>();
        //Arrays.asList(flag, targerPath.getAbsolutePath(), "-encoding", "UTF-8");
        options.add(flag);
        options.add(targerPath.getAbsolutePath());
        options.add("-encoding");
        options.add("UTF-8");
//        options.add("-J-Xss64m");
        // options.add("-J-Xms48m");
        //不使用SharedNameTable （jdk1.7自带的软引用，会影响GC的回收，jdk1.9已经解决）
        options.add("-XDuseUnsharedTable");
        if (sb.toString().length() > 0) {
            options.add("-classpath " + sb);
        }
        log.info("compiler options: {}", options);
        log.info("customized java-code compiler classpath: {}", sb.toString());

        Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFiles);
        result = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits).call();
        for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
            String source = "";
            if (diagnostic.getSource() != null) {
                errorSourceFiles.add(diagnostic.getSource().getName());
                source = StringUtils.substringAfterLast(diagnostic.getSource().getName(), File.separator);
            }
            compileResult.add(source + " 第" + diagnostic.getLineNumber() + "行：" + diagnostic.getMessage(Locale.CHINA));
        }
        return result;
    }

    /// ======================================= create jar file ========================================================
    public boolean createJar(File classPath, File jarFile) throws IOException {
        if (classPath == null || !classPath.exists()) {
            return false;
        }
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
        manifest.getMainAttributes().putValue("Main-Class", "Show");//指定Main Class

        //程序结束后通过以下代码删除生成的jar文件
//        final File jarFile = File.createTempFile("edwin-", ".jar", jarPath);
//		Runtime.getRuntime().addShutdownHook(new Thread() {
//			public void run() {
//				jarFile.delete();
//			}
//		});
//        FileOutputStream fileOutputStream = null;
//        JarOutputStream out = null;
        try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jarFile), manifest)) {
//            fileOutputStream = new FileOutputStream(jarFile);
//            out = new JarOutputStream(fileOutputStream, manifest);
            createJarInner(out, classPath, "");
            out.flush();
            return true;
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    private void createJarInner(JarOutputStream out, File classPath, String base) throws IOException {
        if (classPath.isDirectory()) {
            File[] fl = classPath.listFiles();
            if (base.length() > 0) {
                base = base + "/";
            }
            for (int i = 0; i < fl.length; i++) {
                createJarInner(out, fl[i], base + fl[i].getName());
            }
        } else {
            out.putNextEntry(new JarEntry(base));
            try (FileInputStream in = new FileInputStream(classPath)) {
                byte[] buffer = new byte[1024];
                int n = in.read(buffer);
                while (n != -1) {
                    out.write(buffer, 0, n);
                    n = in.read(buffer);
                }
            }
        }
    }

    ///======================================== Delete Path ============================================================
    public void deleteFileOrDirectory(File file) {
        if (null != file) {
            if (!file.exists()) {
                return;
            }
            int i;
            // file 是文件
            if (file.isFile()) {
                boolean result = file.delete();
                // 限制循环次数，避免死循环
                for (i = 0; !result && i++ < 10; result = file.delete()) {
                    // 垃圾回收
                    System.gc();
                }
                return;
            }

            // file 是目录
            File[] files = file.listFiles();
            if (null != files) {
                for (i = 0; i < files.length; ++i) {
                    deleteFileOrDirectory(files[i]);
                }
            }
            file.delete();
        }
    }

    public String getCompilerClassPath() {
        return compilerClassPath;
    }

    public void setCompilerClassPath(String compilerClassPath) {
        this.compilerClassPath = compilerClassPath;
    }
}
